namespace Org.Loon.Framework.Xna.Game.Core.Graphics.OpenGL.Particle
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;

    public abstract class ParticleSystem : DrawableGameComponent
    {

        public const int AlphaBlendDrawOrder = 100;
        public const int AdditiveDrawOrder = 200;

        private Game game;
        private SpriteBatch spriteBatch;

        public Color color = Color.White;

        public bool isCollidable = false;

        public bool blocksBullets = false;

        public bool blocksLasers = false;

        public int damage;

        private Texture2D texture;

        private Vector2 origin;

        private int howManyEffects;

        public bool hasFinishedRendering = false;

        public Particle[] particles;

        Queue<Particle> freeParticles;

        public static Random randomGenerator = new Random();

        public int FreeParticleCount
        {
            get { return freeParticles.Count; }
        }

        protected int minNumParticles;
        protected int maxNumParticles;

        protected string textureFilename;

        protected float minInitialSpeed;
        protected float maxInitialSpeed;

        protected float minAcceleration;
        protected float maxAcceleration;

        protected float minRotationSpeed;
        protected float maxRotationSpeed;

        protected float minLifetime;
        protected float maxLifetime;

        protected float minScale;
        protected float maxScale;

        protected BlendState spriteBlendMode = BlendState.AlphaBlend;

        protected ParticleSystem(Game game, int howManyEffects)
            : base(game)
        {
            this.game = game;
            this.howManyEffects = howManyEffects;
        }

        public override void Initialize()
        {
            InitializeConstants();

            particles = new Particle[howManyEffects * maxNumParticles];
            freeParticles = new Queue<Particle>(howManyEffects * maxNumParticles);
            for (int i = 0; i < particles.Length; i++)
            {
                particles[i] = new Particle();
                particles[i].Sphere = new BoundingSphere();
                freeParticles.Enqueue(particles[i]);
            }


            base.Initialize();
        }

        protected abstract void InitializeConstants();

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(game.GraphicsDevice);

            if (string.IsNullOrEmpty(textureFilename))
            {
                string message = "textureFilename wasn't set properly, so the " +
                    "particle system doesn't know what texture to load. Make " +
                    "sure your particle system's InitializeConstants function " +
                    "properly sets textureFilename.";
                throw new InvalidOperationException(message);
            }

            texture = game.Content.Load<Texture2D>(textureFilename);

            origin.X = texture.Width / 2;
            origin.Y = texture.Height / 2;

            base.LoadContent();
        }

        public void AddParticles(Vector2 where)
        {
            int numParticles =
                randomGenerator.Next(minNumParticles, maxNumParticles);

            for (int i = 0; i < numParticles && freeParticles.Count > 0; i++)
            {
                Particle p = freeParticles.Dequeue();
                InitializeParticle(p, where);
            }
        }

        public List<BoundingSphere> GetActiveParticlesSpheres()
        {
            List<BoundingSphere> result = new List<BoundingSphere>();
            for (int i = 0; i < particles.Length; i++)
                if (particles[i].Active)
                    result.Add(particles[i].Sphere);

            return result;
        }
  
        protected virtual void InitializeParticle(Particle p, Vector2 where)
        {
            Vector2 direction = PickRandomDirection();

            float velocity =
                Particle.RandomBetween(minInitialSpeed, maxInitialSpeed);
            float acceleration =
                Particle.RandomBetween(minAcceleration, maxAcceleration);
            float lifetime =
                Particle.RandomBetween(minLifetime, maxLifetime);
            float scale =
                Particle.RandomBetween(minScale, maxScale);
            float rotationSpeed =
                Particle.RandomBetween(minRotationSpeed, maxRotationSpeed);

            p.Initialize(
                where, velocity * direction, acceleration * direction,
                lifetime, scale, rotationSpeed);

            if (isCollidable)
            {
                p.Sphere.Radius = (texture.Width / 2) * scale;
                p.Sphere.Center.X = where.X;
                p.Sphere.Center.Y = where.Y;
            }
        }

        protected virtual Vector2 PickRandomDirection()
        {
            float angle = Particle.RandomBetween(0, MathHelper.TwoPi);
            return new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
        }

        public override void Update(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (particles.Length != 0)
            {
                foreach (Particle p in particles)
                {

                    if (p.Active)
                    {
                        p.Update(dt);

                        if (isCollidable)
                        {
                            Vector3 _oldPos = p.Sphere.Center;
                            Vector3 _newPos = new Vector3();

                            _newPos.X = p.Position.X;
                            _newPos.Y = p.Position.Y;
                            _newPos.Z = 0;
                            p.Sphere = p.Sphere.Transform(Matrix.CreateTranslation(_newPos - _oldPos));
                        }

                        if (!p.Active)
                        {
                            freeParticles.Enqueue(p);
                        }
                    }
                }
            }
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {

            GLEx.Device.BlendState = spriteBlendMode;

            spriteBatch.Begin();

            foreach (Particle p in particles)
            {

                if (!p.Active)
                {
                    continue;
                }

                float normalizedLifetime = p.TimeSinceStart / p.Lifetime;

     
                float alpha = 4 * normalizedLifetime * (1 - normalizedLifetime);
                color = new Color(color.R, color.G, color.B, alpha);

                float scale = p.Scale * (.75f + .25f * normalizedLifetime);

                if (isCollidable)
                {
        
                    p.Sphere.Radius = scale * (texture.Width / 2); ;
                }

                spriteBatch.Draw(texture, p.Position, null, color,
                    p.Rotation, origin, scale, SpriteEffects.None, 0.0f);
            }

            spriteBatch.End();

            GLEx.Device.BlendState = BlendState.AlphaBlend;

            base.Draw(gameTime);
        }
    }
}